using System;
using System.Collections.Generic;
using System.Text;
using System.Linq;
using SimpleServiceBus.Bus.Pipeline.PipelineComponents;
using SimpleServiceBus.Bus.Pipeline;
using Apache.NMS;
using SimpleServiceBus.Bus.MessageManagement.Serialization;
using System.Xml;
using SimpleServiceBus.Bus;
using System.Globalization;
using System.IO;
using System.ComponentModel;
using System.Collections;

namespace SimpleServiceBus.Endpoints.ActiveMQ.Transport {

    public class ActiveMQMessageBodySerializationComponent: MessageBodySerializationComponent {

        //-----< Fields >-----
        //----------------------------------------------------------------------------------        


        //-----< Constructors >-----
        //----------------------------------------------------------------------------------        
        public ActiveMQMessageBodySerializationComponent() {
        }

        //-----< Public and protected properties >-----
        //----------------------------------------------------------------------------------        


        //-----< Public and protected methods >-----
        //----------------------------------------------------------------------------------        

        protected override void ProcessMessage(MessageEnvelope messageEnvelope, IMessageContext context) {
            var _activeMQEnvelope = (ActiveMQMessageEnvelope)messageEnvelope;

            if(Pipeline.PipelineDirection == PipelineDirection.Receive) {

                var _bytesMessage = _activeMQEnvelope.TransportMessage as IBytesMessage;
                if(_bytesMessage != null) {
                    if(ReceiveBytesMessage(_bytesMessage, _activeMQEnvelope, context))
                        return;
                }
                else if(_activeMQEnvelope.TextPayload != null) {
                    var svc = context.GetLocalService<IMessageSerializer>();
                    if(svc != null) {
                        var _xmlSerializer = svc as IXmlMessageSerializer;
                        if(_xmlSerializer != null) {
                            messageEnvelope.Body = DeserializeFromXml(_activeMQEnvelope.TextPayload, _xmlSerializer);
                            return;
                        }
                    }
                }
            }
            else if(Pipeline.PipelineDirection == PipelineDirection.Send) {

                if(messageEnvelope.Body != null) {
                    if(SendBytesMessage(_activeMQEnvelope, context))
                        return;
                    else {
                        var svc = context.GetLocalService<IMessageSerializer>();
                        if(svc != null) {
                            var _xmlSerializer = svc as IXmlMessageSerializer;
                            if(_xmlSerializer != null) {
                                _activeMQEnvelope.TextPayload = SerializeToXml(messageEnvelope, _xmlSerializer);
                                return;
                            }
                        }
                    }
                }
            }
            base.ProcessMessage(messageEnvelope, context);
        }

        protected Boolean ReceiveBytesMessage(IBytesMessage bytesMessage, ActiveMQMessageEnvelope messageEnvelope, IMessageContext context) {
            var _binaryStream = bytesMessage.Properties.GetString("BinaryStream");
            if(_binaryStream != null) {
                var _messageClass = bytesMessage.Properties.GetString("MessageClass");
                Object _message;
                if(_messageClass != null) {
                    var _messageType = Type.GetType(_messageClass, true);
                    _message = Activator.CreateInstance(_messageType);
                    foreach(var _property in _message.GetType().GetProperties()) {
                        var _value = bytesMessage.Properties[_property.Name];
                        if(_value != null) {
                            if(_value.GetType() != _property.PropertyType) {
                                var _text = _value as String;
                                if(_text != null) {
                                    if(_property.PropertyType == typeof(DateTime))
                                        _value = DateTime.ParseExact(_text, "o", CultureInfo.InvariantCulture).ToUniversalTime();
                                    else {
                                        var _converter = TypeDescriptor.GetConverter(_property.PropertyType);
                                        if(_converter != null && _converter.CanConvertFrom(_value.GetType()))
                                            _value = _converter.ConvertFromInvariantString(_text);
                                    }
                                }
                                var _list = _value as IList;
                                if(_list != null) {
                                    if(_property.PropertyType.IsArray) {
                                        var _array = Array.CreateInstance(_property.PropertyType.GetElementType(), _list.Count);
                                        for(Int32 i = 0; i < _array.Length; i++)
                                            _array.SetValue(_list[i], i);
                                        _value = _array;
                                    }
                                }
                            }
                            _property.SetValue(_message, _value, null);
                        }
                    }
                    var _bytesProperty = _message.GetType().GetProperty("Bytes");
                    if(_bytesProperty == null)
                        throw new InvalidOperationException(String.Format(CultureInfo.InvariantCulture, "Could not find Bytes property in type {0}", _messageType));
                    if(_bytesProperty.PropertyType != typeof(Byte[]))
                        throw new InvalidOperationException(String.Format(CultureInfo.InvariantCulture, "Expected Bytes property of type {0} to be typed as Byte[]", _messageType));
                    var _content = messageEnvelope.Payload; // Note that there is a bug in NMS.ActiveMQ version 1.3 which prevents us from reading the message's content several times, and thus we must rely on the payload which was assigned by the transport.
                    _bytesProperty.SetValue(_message, _content, null);
                }
                else
                    _message = new BinaryStreamMessage(bytesMessage.Content);
                messageEnvelope.Body = _message;
                return true;
            }
            return false;
        }
        
        protected Boolean SendBytesMessage(ActiveMQMessageEnvelope messageEnvelope, IMessageContext context) {
            var _property = messageEnvelope.Body.GetType().GetProperty("Bytes");
            if(_property != null && _property.PropertyType == typeof(Byte[]) && _property.CanRead) {
                messageEnvelope.Payload = (Byte[])_property.GetValue(messageEnvelope.Body, null);
                messageEnvelope.BytesMessage = messageEnvelope.Payload != null;
                return true;
            }
            return false;
        }

        protected Object DeserializeFromXml(String xml, IXmlMessageSerializer serializer) {
            try {
                using(var _reader = XmlReader.Create(new StringReader(xml))) {
                    return serializer.Deserialize(_reader);
                }
            }
            catch(Exception ex) {
                throw new Exception("There was a problem deserializing a message:" + ex.Message, ex);
            }
        }

        protected String SerializeToXml(MessageEnvelope messageEnvelope, IXmlMessageSerializer serializer) {
            try {
                var _output = new StringBuilder();
                using(var _writer = XmlWriter.Create(_output)) {
                    serializer.Serialize(messageEnvelope.Body, _writer);
                }
                return _output.ToString();
            }
            catch(Exception ex) {
                throw new Exception("There was a problem serializing a message:" + ex.Message, ex);
            }
        }

        //-----< Events >-----
        //----------------------------------------------------------------------------------        


        //-----< Internal members >-----
        //----------------------------------------------------------------------------------        


        //-----< Private members >-----
        //----------------------------------------------------------------------------------        


        //-----< All nested types >-----
        //----------------------------------------------------------------------------------        


        //-----< All explicit interface implementations >-----
        //----------------------------------------------------------------------------------        


    }
}

